package com.flyduck.common.aspect;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.flyduck.common.annotation.Log;
import com.flyduck.modules.system.domain.SysLog;
import com.flyduck.modules.system.domain.SysUser;
import com.flyduck.modules.system.mapper.SysLogMapper;
import com.flyduck.common.util.RequestHolder;
import com.flyduck.common.util.StringUtils;
import com.flyduck.common.util.ThrowableUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;

/**
 * @program: flyduck-admin
 * @description: 日志切面
 * @author: flyduck
 * @create: 2020-05-10 19:12
 **/
@Aspect
@Component
@Slf4j
public class LogsAspect {

    @Autowired
    private SysLogMapper sysLogMapper;

    @Pointcut("@annotation(com.flyduck.common.annotation.Log)")
    public void logPointCut() {
    }

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("开始记录日志");
        Object proceed = joinPoint.proceed();
        SysLog sysLog = new SysLog();
        //1.设置日志的参数
        //=====设置Ip、方法、参数、登录人、描述
        setCommonParams(sysLog,joinPoint);
        //=====日志类型
        sysLog.setLogType("INFO");
        //=====异常信息
        sysLog.setExceptionDetail("");
        //2.保存日志
        sysLogMapper.insert(sysLog);
        log.info("记录日志结束");
        return proceed;
    }

    /**
     * 配置异常通知
     *
     * @param joinPoint join point for advice
     * @param e exception
     */
    @AfterThrowing(pointcut = "logPointCut()", throwing = "e")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable e) throws Throwable{
        log.info("开始记录日志");
        //1.设置日志的参数
        SysLog sysLog = new SysLog();
        //=====设置Ip、方法、参数、登录人、描述
        setCommonParams(sysLog,joinPoint);
        //=====日志类型
        sysLog.setLogType("ERROR");
        //=====异常信息
        sysLog.setExceptionDetail(ThrowableUtil.getStackTrace(e));
        //2.保存日志
        sysLogMapper.insert(sysLog);
        log.info("记录日志结束");
    }

    /**
     * 设置Ip、方法、参数、登录人、描述
     * @param sysLog
     */
    private void setCommonParams(SysLog sysLog,JoinPoint joinPoint) throws Exception{
        //1.操作时间
        sysLog.setTime(new Date());
        //2.IP
        sysLog.setIp(getIp());
        //3.方法
        sysLog.setMethod(getMethod(joinPoint));
        //4.参数
        String params = new ObjectMapper().writeValueAsString(joinPoint.getArgs());
        sysLog.setParams(params);
        //5.设置登录人
        sysLog.setUsername(getLoginName(joinPoint));
        //6.描述
        String desc = getDesc(joinPoint);
        sysLog.setDescription(desc);
        log.info("{}方法的入参为:{}",desc,params);
    }

    /**
     * 获取请求IP
     * @return
     */
    private String getIp(){
        HttpServletRequest httpServletRequest = RequestHolder.getHttpServletRequest();
        String ip = StringUtils.getIp(httpServletRequest);
        return ip;
    }


    /**
     * 获取方法
     * @param joinPoint
     * @return
     */
    private String getMethod(JoinPoint joinPoint){
        //1.获取全路径和名称
        //=====获取目标方法的全路径
        String methodPath = joinPoint.getTarget().getClass().getName();
        //=====获取方法的名称
        String methodName = joinPoint.getSignature().getName();
        //2.组装方法
        String method = methodPath+":"+methodName;
        return method;
    }

    /**
     * 获取描述
     * 1.getsDeclaredMethod会返回类所有声明的字段，包括private、protected、public，但是不包括父类的
     * 2.getMethods(）:则会返回包括父类的所有的public方法，和getFields一样
     * @return
     */
    private String getDesc(JoinPoint joinPoint) throws Exception {
        //1.方法名称
        String methodName = joinPoint.getSignature().getName();
        // 2.获取类
        String targetName = joinPoint.getTarget().getClass().getName();
        Class targetClass = Class.forName(targetName);
        // 3.参数
        Object[] arguments = joinPoint.getArgs();
        //4.获取描述
        Method[] methods = targetClass.getMethods();
        String description = "";
        for (Method method1 : methods) {
            if (method1.getName().equals(methodName)) {
                Class[] clazzs = method1.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    // 获取description
                    description = method1.getAnnotation(Log.class).value();
                    break;
                }
            }
        }
        return description;
    }

    /**
     * 获取登录人
     * @param joinPoint
     * @return
     */
    private String getLoginName(JoinPoint joinPoint){
        SysUser sysUser = (SysUser) SecurityUtils.getSubject().getPrincipal();
        String name = joinPoint.getSignature().getName();
        if ("login".equals(name)) {
            return "";
        }
        return sysUser.getUsername();
    }


}
